// Copyright (c) 2023 Institute of Software, Chinese Academy of Sciences
// Ripple is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
//          http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

package ripple.server.core.overlay;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ripple.common.DebugTool;
import ripple.common.entity.AbstractMessage;
import ripple.common.entity.NodeMetadata;
import ripple.common.entity.Towards;
import ripple.server.core.overlay.tree.TreeOverlay;


import java.io.IOException;
import java.util.*;


public class FicusOverlay implements Overlay {


    private static final Logger LOGGER = LoggerFactory.getLogger(FicusOverlay.class);

    public static final int MAX_SERVER_COUNT = 100000;

    public static final int MAX_CLOUD_GROUP = 10;

    public static String CLOUD = "Cloud";

    public static String EDGE = "Edge";

    private static String NORMAL = "Normal";
    private static String PROPOSER = "Proposer";
    private static String FOLLOWER = "Follower";
    private static String LEADER = "Leader";

    private List<NodeMetadata> nodeList;

    public String getOverlayArg() {
        return overlayArg;
    }

    public void setOverlayArg(String overlayArg) {
        this.overlayArg = overlayArg;
    }

    private String overlayArg;


    public List<NodeMetadata> getNodeList() {
        return nodeList;
    }

    public void setNodeList(List<NodeMetadata> nodeList) {
        this.nodeList = nodeList;
    }

    public FicusOverlay(String overlayArg) {
        this.setOverlayArg(overlayArg);
    }

    public FicusOverlay() {
        this("tree,3");
    }


    public HashMap<Integer, List<NodeMetadata>> getGroupMap() {
        return groupMap;
    }

    public void setGroupMap(HashMap<Integer, List<NodeMetadata>> groupMap) {
        this.groupMap = groupMap;
    }

    private HashMap<Integer, List<NodeMetadata>> groupMap;

    public Map<Integer, Overlay> getGroupOverlay() {
        return groupOverlay;
    }

    public void setGroupOverlay(Map<Integer, Overlay> groupOverlay) {
        this.groupOverlay = groupOverlay;
    }

    private Map<Integer, Overlay> groupOverlay;

    private String state;

    private FicusOverlay newOverLay;

    private Queue<String> waitQueue;


    public void AbortOldOverlay() {

    }

    public int getTerm() {
        return term;
    }

    public void setTerm(int term) {
        this.term = term;
    }

    private int term;

    @Override
    public void buildOverlay(List<NodeMetadata> nodeList) {
        this.setNodeList(nodeList);
        this.setGroupMap(new HashMap<>());


//        DebugTool.write("{[overlay nodeList size:" + nodeList.size() + "]}");
//        Map<Integer, List<NodeMetadata>> groupNodeList = new HashMap<>();
        for (NodeMetadata node : nodeList) {
//            DebugTool.write("{[node:" + node.getId() + "],[group id:" + node.getGroupId() + "]}");
            int groupId = node.getGroupId();
            if (this.getGroupMap().containsKey(groupId)) {
                this.getGroupMap().get(groupId).add(node);
            } else {
                List<NodeMetadata> group = new ArrayList<>();
                group.add(node);
                this.getGroupMap().put(groupId, group);
            }
        }


        String[] args = this.getOverlayArg().split(",");
        this.setGroupOverlay(new HashMap<>());
        for (Integer groupId : this.getGroupMap().keySet()) {
//            Overlay groupOverlay = switch (args[0]) {
//                case "star" -> new StarOverlay();
//                case "tree" -> new TreeOverlay(Integer.parseInt(args[1]));
//                default -> new StarOverlay();
//            }
            Overlay groupOverlay;
//            System.out.println("args[0]: " + args[0]);
            if (Objects.equals(args[0], "star")) {
//                System.out.println("new StarOverlay()");
                groupOverlay = new StarOverlay();
            } else if (Objects.equals(args[0], "tree")) {
                groupOverlay = new TreeOverlay(Integer.parseInt(args[1]));
            } else {
                groupOverlay = new TreeOverlay(3);
            }

            groupOverlay.buildOverlay(this.getGroupMap().get(groupId));
            this.getGroupOverlay().put(groupId, groupOverlay);

//            DebugTool.write("{[groupId:" + groupId + "],[nodeList size:" + this.getGroupMap().get(groupId).size() + "]}");


        }
    }

    @Override
    public List<NodeMetadata> calculateNodesToSync(AbstractMessage message, NodeMetadata source, NodeMetadata current) {
        LOGGER.info("[FicusOverlay] calculateNodesToSync() called. sourceId = {}, currentId = {}", source.getId(), current.getId());
        return this.getGroupOverlay().get(current.getGroupId()).calculateNodesToSync(message, source, current);
//        if (message.getOverLayerTerm() == this.getTerm()) {
//            return this.getGroupOverlay().get(current.getGroupId()).calculateNodesToSync(message, source, current);
//        } else if (this.newOverLay != null && message.getOverLayerTerm() == this.newOverLay.getTerm()) {
//            return this.getNewOverLay().getGroupOverlay().get(current.getGroupId()).calculateNodesToSync(message, source, current);
//        }
//        return new ArrayList<NodeMetadata>();
    }

    @Override
    public List<NodeMetadata> calculateNodesToCollectAck (AbstractMessage message){
        return this.getNodeList();
    }

    private int getParentGroupId ( int groupId){
        return groupId % 10;
    }


    public List<NodeMetadata> calculateOtherGroupNodeToSync (AbstractMessage message, NodeMetadata
            source, NodeMetadata current){
        LOGGER.info("[FicusOverlay] calculateNodeToSync() called. sourceId = {}, currentId = {}", source.getId(), current.getId());
        List<NodeMetadata> ret = new ArrayList<>();
        if (Objects.equals(message.getTowards(), Towards.E2C)) {
            List<NodeMetadata> parentGroupList = this.getGroupMap().get(getParentGroupId(current.getGroupId()));
//            NodeMetadata parentNode = parentGroupList.get(current.getId() % parentGroupList.size());
            if (parentGroupList != null && parentGroupList.size() > 0) {
                ret.add(parentGroupList.get(current.getId() % parentGroupList.size()));
            }
        } else if (Objects.equals(message.getTowards(), Towards.C2E)) {
            if (isCloudGroupId(current.getGroupId())) {
                for (Integer groupId : this.getGroupMap().keySet()) {
                    if (getParentGroupId(groupId) == current.getGroupId() && groupId != current.getGroupId()) {
                        this.getGroupMap().get(groupId);
                    }
                }

            }
        }

        return this.getGroupOverlay().get(current.getGroupId()).calculateNodesToSync(message, source, current);
    }

    public List<List<NodeMetadata>> calculateGroupsToSync (AbstractMessage message, NodeMetadata source, NodeMetadata current){
        LOGGER.info("[FicusOverlay] calculateGroupsToSync() called. sourceId = {}, currentId = {}", source.getId(), current.getId());
        List<List<NodeMetadata>> groups = new ArrayList<>();
        if (Objects.equals(message.getTowards(), "E2C")) {
            groups.add(this.getGroupMap().get(this.getParentGroupId(current.getGroupId())));
        } else if (Objects.equals(message.getTowards(), "C2E")) {
            if (isCloudGroupId(current.getGroupId())) {
                for (Integer groupId : this.getGroupMap().keySet()) {
                    if (getParentGroupId(groupId) == current.getGroupId() && groupId != current.getGroupId()) {
                        groups.add(this.getGroupMap().get(groupId));
                    }
                }
            }
        }
        return groups;
    }

    private boolean isCloudGroupId ( int groupId){
        return groupId < 10;
    }

    public List<NodeMetadata> getGroupNodeList ( int groupId){
        return this.getGroupMap().get(groupId);
    }

    @Override
    public int getMaxLoad() {
        return Integer.MAX_VALUE;
    }
}


